<?php /* Copyright 2014 Karl R. Wilcox

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

abstract class grammar  {
  const ERROR_ORIGIN = 'parser';
  const PRIORWORDS = 6;

  protected $tokenList;
  protected $schema;
  protected $patternDB;
  protected $phraseMatcher;
  protected $messages = array();
  protected $stateStore;
  protected $language;
  
  public function __construct( $language, $schema) {
    $this->language = $language;
    include "$language/lexicon.inc";
    $this->patternDB = new languageDB();
    $this->stateStore = new persistentStore();
    $this->schema = $schema;
  }
 
  abstract function getShield();
  
  public function readGrammar ( $tokenList ) {
    $this->phraseMatcher = new matcher($tokenList, $this->patternDB);
    $this->tokenList = $tokenList;
    return $this->getShield();
  }

  // shorthand functions to make code read better
  protected function ignore ( $key ) { $this->phraseMatcher->searchMatch($key); }
  protected function find ( $key ) { return $this->phraseMatcher->searchMatch($key); }
  protected function tokens () { return $this->phraseMatcher->getMatchedTokens(); }
  protected function lookAhead ( $offset ) { return $this->phraseMatcher->lookahead( $offset ); }
  protected function moreInput() { return $this->tokenList->moreInput(); }
    
  // Interface implementation
  public function save() {
    return ( $this->stateStore->store($this->tokenList->cur_word) );
  }
  
  public function restore($state) {
    global $trace;
    
    $data = $this->stateStore->restore($state);
    if ( $trace ) echo "<p>Restore to $data</p>\n";
    if ($data !== null ) {
      $this->tokenList->cur_word = $data;
    } else {
      self::raiseError ( 'internal', 'No state to restore' );
    }
  }
  
  public function discard($state) {
    $this->stateStore->discard($state);
  }
  
  protected function raiseError ( $type, $message, $getOffset = false ) {
    $context = '';
    if ( $getOffset === true ) {
      $tokens = $this->tokenList->getTokens();
      $offset = $this->phraseMatcher->getTokenOffset();
      $start = $offset - self::PRIORWORDS;
         $start = $start < 0 ? 0 : $start; 
         for ( $i = $start; $i < $offset; $i++ ) 
           $context .= $tokens[$i] . ' ';
    } elseif ( $getOffset !== false )
      $context = $getOffset;
    $this->messages[] = array ($type, $message, $context );
    // TODO suppress duplicate errors?
  }
  
  protected function clearErrors( $type = null ) {
    if ( $type == null )
      $this->messages = array();
    else
      for ($i = count ($this->messages) - 1; $i >= 0; $i--) {
        if ($this->messages[$i][0] == $type)
          array_splice($this->messages,$i,1);
    }
  }

  public function getMessages () {
    // Was there any unknown input?
    if ( $this->tokenList->badWords )
      self::raiseError('blazon',"Don't understand words in brackets", implode(' ',$this->tokenList->getTokens()));
    // include any messages from the lexical analyser
    return array_merge($this->messages, $this->phraseMatcher->getMatcherErrors());
  }
      
  protected function semicolon($lookback = true) {
  
    $retval = false;
    if ( self::find(languageDB::SEMICOLON) ) 
      $retval = true;
    if ( $lookback ) {
      $state = self::save();
      if ( self::lookAhead(-1) && self::find(languageDB::SEMICOLON) != null ) $retval = true;
      self::restore($state);
    } 
    return $retval;
  }
  
  
  protected function comma() {
    
    // Consume a comma (or similar) character if present, and return true
    // But also return true if the *previous* character was a comma
    if ( self::find(languageDB::COMMA) ) return true;
    $state = self::save();
    if ( self::lookAhead(-1) && self::find(languageDB::COMMA) ) { self::discard($state); return true; }
    self::restore($state); 
    return false;
  }
  
  protected function restoreAnd() {
    // If the last word was "and", put it back again
    $state = self::save();
    self::lookAhead(-1);
    if ($keyterm = self::find(languageDB::ANDD)) {
      self::lookAhead(-1);
      self::discard($state);
    } else
        self::restore($state);
  }
}

?>
